package org.ndx.jsg.output.freemarker;

import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.configuration.Configuration;
import org.ndx.jsg.FileInfos;
import org.ndx.jsg.MimeTypes;
import org.ndx.jsg.config.DefaultConfiguration;
import org.ndx.jsg.input.DefaultFileInfosProvider;
import org.ndx.jsg.input.xml.JSGXmlBuilder;
import org.ndx.jsg.output.FileCopier;
import org.ndx.jsg.output.OutputProvider;
import org.ndx.jsg.util.JsgException;
import org.xml.sax.SAXException;

import com.google.inject.Inject;

import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class JsgXmlTransformer implements OutputProvider, TemplateProvider {
	public static final String JSG_TEMPLATES_EXTENSION = "jsg.templates.extension";
	public static final String JSG_LIBRARIES_EXTENSION = "jsg.libraries.extension";
	private static Logger logger = Logger.getLogger(JsgXmlTransformer.class
			.getName());
	/**
	 * Templates used to process all files in one shot
	 */
	private Collection<Template> globalTemplates = new LinkedList<Template>();

	/**
	 * Templates used to process all files of each directories at the same time
	 */
	private Collection<Template> directoriesTemplates = new LinkedList<Template>();

	/**
	 * Templates used to process one file and dump the output
	 */
	private Collection<Template> fileTemplates = new LinkedList<Template>();

	/**
	 * Output root, memorized for easier processing
	 */
	private String outputRoot;

	/**
	 * Data structure arranging processable files the best possible way.
	 */
	@Inject
	private ProcessableFiles processableFiles;

	@Override
	public void prepare(Configuration config) {
		freemarker.template.Configuration freemarkerConfig = new freemarker.template.Configuration();
		outputRoot = config.getString(DefaultConfiguration.OUTPUT_PATH);
		// Load configuration directory
		String templatesPath = getTemplatesPath(config);
		// List all already loaded templates names
		Collection<String> loadedTemplates = new LinkedList<String>();
		File templates = new File(templatesPath);
		if (!templates.exists()) {
			File configPath = new File(config
					.getString(DefaultConfiguration.CONFIGURATION_FILE));
			templates = new File(configPath.getParent() + File.separator
					+ templatesPath);
		}
		copyNonTemplates(templates, config);
		try {
			freemarkerConfig.setDirectoryForTemplateLoading(templates);
			freemarkerConfig.setObjectWrapper(new DefaultObjectWrapper());
			// Extract global tempaltes
			loadedTemplates.addAll(loadTemplates(globalTemplates, config
					.getStringArray(DefaultConfiguration.JSG_TEMPLATES_GLOBAL),
					freemarkerConfig));
			loadedTemplates
					.addAll(loadTemplates(
							directoriesTemplates,
							config
									.getStringArray(DefaultConfiguration.JSG_TEMPLATES_DIRECTORIES),
							freemarkerConfig));
			// All other templates having the desired template extension
			// extension are loaded as file templates
			loadTemplates(fileTemplates, getOtherTemplates(templates, getTemplatesExtension(config),
					loadedTemplates), freemarkerConfig);
		} catch (Exception e) {
			throw new JsgException(
					"unable to prepare freemarker using template directory "
							+ templates.getAbsolutePath(), e);
		}
	}

	public static String getTemplatesExtension(Configuration config) {
		return config
				.getString(JSG_TEMPLATES_EXTENSION, ".template");
	}

	public static String getLibrariesExtension(Configuration config) {
		return config
				.getString(JSG_LIBRARIES_EXTENSION, ".ftl");
	}

	public static String getTemplatesPath(Configuration config) {
		return config.getString(DefaultConfiguration.JSG_TEMPLATES_DIRECTORY,
				"templates");
	}

	/**
	 * Build an array of file names from a source path, a template extension,
	 * and the exclusion of all already loaded templates
	 * 
	 * @param templatesDir
	 *            storage directory for templates
	 * @param extension
	 *            template extension
	 * @param loadedTemplates
	 *            already loaded templates that should not reside in this
	 *            collection
	 * @return a list of remaining templates, that should be considered as file
	 *         templates
	 */
	private String[] getOtherTemplates(File templatesDir,
			final String extension, final Collection<String> loadedTemplates) {
		File[] remaining = templatesDir.listFiles(new FilenameFilter() {

			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(extension)
						&& !loadedTemplates.contains(name);
			}
		});
		String[] names = new String[remaining.length];
		for (int index = 0; index < names.length; index++) {
			names[index] = remaining[index].getName();
		}
		return names;
	}

	/**
	 * Load templates and return their names
	 * 
	 * @param destination
	 * @param templatesNames
	 *            will theorically be returned
	 * @param freemarkerConfig
	 *            used config
	 * @return collection of eccetively loaded templates
	 * @throws IOException
	 */
	private Collection<String> loadTemplates(Collection<Template> destination,
			String[] templatesNames,
			freemarker.template.Configuration freemarkerConfig)
			throws IOException {
		Collection<String> returned = new LinkedList<String>();
		for (String name : templatesNames) {
			Template template = freemarkerConfig.getTemplate(name);
			destination.add(template);
			returned.add(name);
		}
		return returned;
	}

	/**
	 * Transform all files using given set of templates. Once it is done, copy
	 * all required files from the template directory to the output directory.
	 */
	@Override
	public void terminate(Configuration config) {
		processableFiles.process(this);
	}

	private void copyNonTemplates(File templates, Configuration config) {
		if(logger.isLoggable(Level.FINE)) {
			logger.fine("copying templatess associated files through direct copy");
		}
		DefaultFileInfosProvider infos = new DefaultFileInfosProvider(
				new NonTemplateBuidler(config));
		Collection<FileInfos> toCopy = infos
				.getFiles(
						templates,
						new String[] {""},
						Arrays
								.asList(config
										.getStringArray(DefaultConfiguration.INPUT_IGNORED_NAMES)));
		FileCopier fileCopier = new FileCopier();
		for(FileInfos f : toCopy) {
			fileCopier.handle(f);
		}
		fileCopier.terminate(config);
	}

	/**
	 * When handling file, we check if its format is correct. If so, we add it
	 * to processableFiles and then TODO extract some metadata from it (like
	 * title, tags, date, ...)
	 */
	@Override
	public void handle(FileInfos infos) {
		if (infos.getFile().getName().endsWith(JSGXmlBuilder.JSG_XML_EXTENSION)) {
			if (logger.isLoggable(Level.FINE)) {
				logger.fine("file " + infos.getRelativePath()
						+ " is marked as processable");
			}
			processableFiles.add(infos);
		}
	}

	public Collection<Template> getGlobalTemplates() {
		return globalTemplates;
	}

	public Collection<Template> getDirectoriesTemplates() {
		return directoriesTemplates;
	}

	public Collection<Template> getFileTemplates() {
		return fileTemplates;
	}

	public String getOutputRoot() {
		return outputRoot;
	}
}
